#!/usr/bin/env bash
# Copyright (C) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE in project root for information.

. "$(dirname "${BASH_SOURCE[0]}")/../../runme"

types=(S M P R D)
declare -A S=([container]="$STORAGE_CONTAINER"
              [path]=""
              [suffix]="/")
declare -A M=([container]="$MAVEN_CONTAINER"
              [path]="com/microsoft/ml/spark/mmlspark_$SCALA_VERSION"
              [suffix]="/")
declare -A P=([container]="$PIP_CONTAINER"
              [path]=""
              [prefix]="mmlspark-"
              [suffix]="-py2.py3-none-any.whl")
declare -A R=([container]="$R_CONTAINER"
              [path]=""
              [prefix]="mmlspark-"
              [suffix]=".zip")
declare -A D=([container]="$DOCS_CONTAINER"
              [path]=""
              [suffix]="")

set -e
shopt -s nullglob

GH_API="https://api.github.com/repos/Azure/mmlspark"
github_prs="$(curl --silent --show-error "$GH_API/pulls" \
              | jq -r 'map(.number | tostring) | " " + join(" ") + " "')"

vsort() { sort -t "+" -k 1,2 -V "$@"; }

now=0; _minute=$((60)); _hour=$((60*60)); _day=$((_hour*24))
show-time() (
  time="$1"
  if [[ ! "$time" =~ ^[0-9]+$ ]]; then time="$(date -d "$time" +"%s")"; fi
  diff=$((now - $time))
  _ago() {
    d=$((diff / $1)) u="$2"; if [[ "$d" != "1" ]]; then u+="s"; fi
    echo "$d $u ago"; }
  if   ((diff < _minute));  then _ago $((1         )) "second"
  elif ((diff < _hour));    then _ago $((_minute   )) "minute"
  elif ((diff < _day));     then _ago $((_hour     )) "hour"
  elif ((diff < 7*_day));   then _ago $((_day      )) "day"
  elif ((diff < 30*_day));  then _ago $((7 * _day  )) "week"
  elif ((diff < 365*_day)); then _ago $((30 * _day )) "month"
  else                           _ago $((365 * _day)) "year"
  fi
)
show-time-range() (
  t1="$(show-time "$1")" t2="$(show-time "$2")"
  if [[ "$t1" = "$t2" ]]; then echo "$t1"; return; fi
  u1="${t1#* }"; u1="${u1% ago}" u1="${u1%s}"
  u2="${t2#* }"; u2="${u2% ago}" u2="${u2%s}"
  if [[ "$u1" != "$u2" ]]; then echo "${t1% ago} -- $t2"; return; fi
  echo "${t1%% *}--${t2%% *} ${u1}s ago"
)

azls() {
  local p="$1"; shift
  if [[ -z "$p" ]]; then failwith "azls: missing argument"; fi
  if [[ "$p" != "/"* ]]; then failwith "azls: absolute argument required"; fi
  p="${p#/}"
  if [[ "$p" = "" ]]; then
    az storage container list | jq -r 'map(.name) | .[]'
  else
    local c="${p%%/*}"; p="${p#$c}"; p="${p#/}"
    local xs=(--delimiter "/"); if [[ -n "$p" ]]; then xs+=(--prefix "$p"); fi
    az storage blob list -c "$c" "${xs[@]}" | \
      jq -r 'map(.name | sub("^'"$p"'"; "")) | .[]'
  fi
}

get-versions-for() {
  declare -n X="$1" Xs="${1}s"
  local IFS=$'\n\r'
  Xs=($(IFS=""; azls "/${X[container]}/${X[path]}${X[path]:+/}" | \
          while read -r l; do
            # if [suffix] is empty, use "/" (works for docs)
            l="${l#${X[prefix]}}"; l="${l%${X[suffix]:-/}}"
            # ignore things that don't look like a version (eg, in docs)
            if [[ "$l" = *[0-9].[0-9]* ]]; then echo "$l"; fi
          done | vsort))
  IFS=" "
  all+=("${Xs[@]}")
  X[vers_]=" ${Xs[*]} "
}

show-not-all() (
  fst=1
  for v in "${all[@]}"; do
    where=""
    for t in "${types[@]}"; do
      declare -n X="$t"
      if [[ "${X[vers_]}" = *" $v "* ]]; then where+="$t"; fi
    done
    if [[ "$where" != "$types_" ]]; then
      if ((fst)); then printf "\nNot all found:\n"; fst=0; fi
      echo "  $v $where"
    fi
  done
)

tmp="/tmp/$(basename "$0")-$$"
cachedir="$HOME/.$(basename "$0")"; mkdir -p "$cachedir"

(cd "$cachedir"
 for f in *; do
   # delete version infos that were deleted
   if [[ ! -s "$f" ]]; then rm -f "$f"; continue; fi
   # or possibly created during their build (recent file close enough to creation time)
   del="$(jq -r 'if .created - .maxtime < 2*60*60 then "x" else "." end' < "$f")"
   if [[ "$del" = "x" ]]; then rm -f "$f"; fi
 done)

get-ver-info() (
  v="$1" info="$tmp-info"
  echo "{}" > "$info"
  if [[ -r "$cachedir/$v" ]]; then return; fi
  for t in "${types[@]}"; do
    declare -n X="$t"
    pfx="${X[path]}${X[path]:+/}${X[prefix]}$v${X[suffix]}"
    az storage blob list -c "${X[container]}" --prefix "$pfx" \
    | jq --slurpfile i "$info" --arg pfx "$pfx" '$i[0] + {"'"$t"'":
        map(select((.name == $pfx)
                   or ((.name | startswith($pfx))
                       and (($pfx | endswith("/"))
                            or (.name | .[($pfx | length):]
                                | startswith("/"))))))}' \
    > "$info-new"; mv "$info-new" "$info"
  done
  #
  label="$(jq -r '.S | map(select(.name | endswith("/.label")) | .name) | .[]' \
                 < "$info")"
  if [[ "$label" = *$'\n'* ]]; then failwith "found multiple .label blobs"; fi
  if [[ -n "$label" ]]; then
    az storage blob download -c "${S[container]}" -n "$label" -f "$tmp" > /dev/null
    label="$(< "$tmp")"
    rm -f "$tmp"
  fi
  jq --arg l "$label" '. + {label: $l}' \
     < "$info" > "$info-new"; mv "$info-new" "$info"
  #
  az storage blob download -c "${S[container]}" -n "$v/Build.md" -f "$tmp.md" > /dev/null
  binfo="$(< "$tmp.md")"; rm -f "$tmp.md"
  if [[ -z "$binfo" ]]; then binfo="(No info: \"Build.md\" file not found)"; fi
  jq --arg b "$binfo" '. + {buildinfo: $b}' \
     < "$info" > "$info-new"; mv "$info-new" "$info"
  #
  rx="\n#+ [^\n]* build [^\n]*github PR #([0-9]+)"
  rx=$'\n'"#+ [^"$'\n'"]* build [^"$'\n'"]*github PR #([0-9]+)"
  if [[ "$binfo" =~ $rx ]]; then pr="${BASH_REMATCH[1]}"; else pr=""; fi
  jq --arg pr "$pr" '. + {github_pr: $pr}' \
     < "$info" > "$info-new"; mv "$info-new" "$info"
  #
  jq --argjson now "$(date +"%s")" \
             '. + (to_entries
                   | map(select((.value | type) == "array") | .value) | add
                   | map(.properties.lastModified | sub("\\+00:00$"; "Z") | fromdate)
                   | sort | {created: $now, mintime: .[0], maxtime: .[-1]})' \
     < "$info" > "$info-new"; mv "$info-new" "$info"
  #
  mv "$info" "$cachedir/$v"
)

show-all-oneline() {
  local v times t1 t2 info pr; now="$(date +"%s")"
  printf "\n"
  for v in "${all[@]}"; do
    if [[ -r "$cachedir/$v" && ! -s "$cachedir/$v" ]]; then continue; fi
    printf '  %-22s' "$v"
    get-ver-info "$v"
    times="$(jq -r '"\(.mintime):\(.maxtime)"' < "$cachedir/$v")"
    printf ' %-16s' "$(show-time-range "${times%:*}" "${times#*:}")"
    info="$(jq -r '.label' < "$cachedir/$v")"
    pr="$(jq -r '.github_pr' < "$cachedir/$v")"
    if [[ -n "$pr" ]]; then
      if [[ "$github_prs" = *" $pr "* ]]
      then pr="PR #$pr"; else pr="Closed PR #$pr"; fi
      info="$pr${info:+"; "}$info"
    fi
    if [[ -z "$info" ]]; then printf '\n'
    else printf ' [%s]\n' "$info"; fi
  done
}

ver="" # most functions use $ver dynamically

do-label() (
  read -rp "  New label line: " l
  if [[ -z "$l" ]]; then
    if az storage blob delete -c "${S[container]}" -n "$ver/.label" > /dev/null
    then echo "  Label Deleted"; else echo "  Failed"; fi
  else
    echo "$l" > "$tmp"
    if az storage blob upload -c "${S[container]}" -n "$ver/.label" \
          -f "$tmp" >& /dev/null
    then echo "  Labeled"; else echo "  Failed"; fi
  fi
  rm -f "$tmp" "$cachedir/$ver"
)

list-blobs() (
  jq -r 'to_entries | map(select((.value | type) == "array") | .key as $k
                          | .value | map(. + {$k}))
         | add | map("\(.k)/\(.name)") | .[]' < "$cachedir/$ver"
)

do-actual-delete() (
  printf '  | Deleting...\n'
  IFS=""; list-blobs | while read path; do
    declare -n X="${path%%/*}"; blob="${path#*/}"
    printf '  |   %s...' "${X[container]}/$blob"
    if az storage blob delete -c "${X[container]}" -n "$blob" > /dev/null
    then echo " deleted"; else echo " failed"; fi
  done
  (> "$cachedir/$ver") # mark as deleted
)

do-delete() (
  for t in "${types[@]}"; do
    declare -n X="$t"; sfx="${X[suffix]}"; cont="${X[container]}"
    blob="${X[path]}${X[path]:+/}${X[prefix]}$ver$sfx"
    _copy() (
      set -o pipefail
      printf '  | Copying %s...\n' "$cont/$1"
      if az storage blob copy start-batch --destination-container="deleted" \
            --source-container "$cont" --pattern "$1" \
          | jq -r 'map(sub("^http.*?//.*?/deleted/"; "  |   ")) | .[]'
      then : printf '  | Done\n'; else printf '  | Fail\n'; return; fi
    )
    if [[ "$sfx" = "/"* ]]; then _copy "$blob*"
    else
      # do the copy of the plain name only if we know it exists, since az will
      # throw an error if a non-* glob doesn't exist
      if [[ "$blob" != *"*"* ]] && list-blobs | grep -q "^$t/$blob\$"
      then _copy "$blob"; fi
      # just for fun, do the same test for "foo/"
      if [[ "$blob" != *"*"* ]] && list-blobs | grep -q "^$t/$blob/"
      then _copy "$blob/*"; fi
    fi
  done
  sleep 4 # just in case?
  # no patterns in the delete cli
  do-actual-delete
)

do-list() (
  IFS=""; list-blobs | while read path; do
    declare -n X="${path%%/*}"; blob="${path#*/}"
    printf '  | %s\n' "${X[container]}/$blob"
  done
)

do-refresh() {
  rm -f "$cachedir/$ver"
}

do-requests() {
  local ver vers c show_all=1
  while { if ((show_all)); then show-all-oneline; fi; show_all=0
          read -r -p $'\n'"Version: " ver; } do
    if [[ -z "$ver" ]]; then break; fi
    if [[ ! -r "$cachedir/$ver" ]]; then
      vers=( "$cachedir/"*"$ver"* )
      if [[ "${#vers[@]}" = "0" ]]; then echo "No such version"; continue; fi
      if [[ "${#vers[@]}" != "1" ]]; then echo "Ambiguous version"; continue; fi
      ver="$(basename "${vers[0]}")"
      echo "=> Version $ver"
    fi
    echo "  |"
    jq -r '.buildinfo' < "$cachedir/$ver" | awk '{ print "  | " $0 }'
    echo "  |"
    show_all=1
    read -p $'  Delete/Label/listBlobs/Refresh? ' c
    case "${c,,}" in
      ("d"*) do-delete  ;;
      ("l"*) do-label   ;;
      ("b"*) do-list    ;;
      ("r"*) do-refresh ;;
      (*)    show_all=0 ;;
    esac
  done
}

types_="${types[*]}"; types_="${types_// /}"; all=()
map get-versions-for "${types[@]}"
all=($(printf '%s\n' "${all[@]}" | vsort -u))

echo "Versions found: ${#all[@]}"

show-not-all
do-requests

printf "\nDone.\n"
